﻿#region

using System;
using System.Collections.Generic;
using System.Linq;
using HouseHoldBudget.Core.Data;
using HouseHoldBudget.Core.Entities.Budgets;
using HouseHoldBudget.Core.Entities.Expenses;
using HouseHoldBudget.Core.Entities.User;

#endregion

namespace HouseHoldBudget.Services.Expenses
{
    public class ExpenseService : IExpenseService
    {
        #region Readonly & Static Fields

        private readonly IRepository<Expense> _expenseRepository;
        private readonly IRepository<Budget> _budgetRepository;
        private readonly IRepository<BudgetUser> _budgetUserRepository;
        #endregion

        #region Constructors

        public ExpenseService(IRepository<Expense> expenseRepository,
                              IRepository<Budget> budgetRepository,
                              IRepository<BudgetUser> budgetUserRepository)
        {
            _expenseRepository = expenseRepository;
            _budgetRepository= budgetRepository;
            _budgetUserRepository = budgetUserRepository;
        }

        #endregion

        #region IExpenseService Members


        /// <summary>
        /// Gets all expenses by budget id.
        /// </summary>
        /// <param name="budgetId">The budget id.</param>
        /// <returns></returns>
        /// <exception cref="System.ArgumentOutOfRangeException"></exception>
        public List<Expense> GetAllExpensesByBudgetId(Guid budgetId)
        {
            var budget = _budgetRepository.GetById(budgetId);
            switch ((BudgetTypes)budget.BudgetTypeId)
            {
                case BudgetTypes.Mensual:
                    return GetAllExpensesByBudgetId(budgetId, DateTime.Now.Year, DateTime.Now.Month);
                case BudgetTypes.Annual:
                    return GetAllExpensesByBudgetId(budgetId, DateTime.Now.Year);
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        /// <summary>
        /// Gets all expenses by budget id.
        /// </summary>
        /// <param name="budgetId">The budget id.</param>
        /// <param name="YearNumber">The year number.</param>
        /// <returns></returns>
        public List<Expense> GetAllExpensesByBudgetId(Guid budgetId, int YearNumber)
        {
            return GetAllExpensesByBudgetId(budgetId,
                new DateTime(DateTime.Now.Year, 1, 1),
                new DateTime(DateTime.Now.Year, 12, 31));
        }

        /// <summary>
        /// Gets all expenses by budget id.
        /// </summary>
        /// <param name="budgetId">The budget id.</param>
        /// <param name="YearNumber">The year number.</param>
        /// <param name="MonthNumber">The month number.</param>
        /// <returns></returns>
        public List<Expense> GetAllExpensesByBudgetId(Guid budgetId, int YearNumber, int MonthNumber)
        {
            return GetAllExpensesByBudgetId(budgetId,
                                            new DateTime(YearNumber, MonthNumber, 1),
                                            new DateTime(YearNumber, MonthNumber,DateTime.DaysInMonth(YearNumber, MonthNumber)));
        }

        /// <summary>
        /// Gets all expenses by budget id.
        /// </summary>
        /// <param name="budgetId">The budget id.</param>
        /// <param name="startDateFilter">The start date filter.</param>
        /// <param name="endDateFilter">The end date filter.</param>
        /// <returns></returns>
        public List<Expense> GetAllExpensesByBudgetId(Guid budgetId, DateTime? startDateFilter, DateTime? endDateFilter)
        {
            //All expenses for the owner
            var query = (from ex in _expenseRepository.Table
                         join b in _budgetRepository.Table on ex.BudgetUserId equals b.BudgetOwnerId
                         where b.Id == budgetId
                         select ex);

            if (startDateFilter.HasValue)
                query = query.Where(p => p.ExpenseOn >= startDateFilter);

            if (endDateFilter.HasValue)
                query = query.Where(p => p.ExpenseOn <= endDateFilter);

            //All expenses by actors
            var budget = _budgetRepository.GetById(budgetId);
            var query2 = budget.BudgetActors.SelectMany(expense => expense.Expenses);

            if (startDateFilter.HasValue)
                query2 = query2.Where(p => p.ExpenseOn >= startDateFilter);

            if (endDateFilter.HasValue)
                query2 = query2.Where(p => p.ExpenseOn <= endDateFilter);


            List<Expense> lstOwner = query.OrderByDescending(p => p.ExpenseOn).ToList().ToList();
            List<Expense> lstActors = query2.OrderByDescending(p => p.ExpenseOn).ToList().ToList();
            return lstOwner.Concat(lstActors).ToList();
        }

        /// <summary>
        /// Gets the expenses by user id.
        /// </summary>
        /// <param name="userId">The user id.</param>
        /// <param name="startDateFilter">The start date filter.</param>
        /// <param name="endDateFilter">The end date filter.</param>
        /// <returns></returns>
        public List<Expense> GetExpensesByUserId(Guid userId, DateTime? startDateFilter, DateTime? endDateFilter)
        {
            var query = _expenseRepository.Table;

            if (startDateFilter.HasValue)
                query = query.Where(p => p.ExpenseOn >= startDateFilter);

            if (endDateFilter.HasValue)
                query = query.Where(p => p.ExpenseOn <= endDateFilter);

            query = query.Where(p => p.BudgetUserId == userId);

            var expenses = query.OrderByDescending(p => p.ExpenseOn).ToList();
            return expenses;
        }

        /// <summary>
        /// Gets the expenses by id.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <returns></returns>
        public Expense GetExpensesById(Guid id)
        {
            return _expenseRepository.GetById(id);
        }

        /// <summary>
        /// Inserts the expense.
        /// </summary>
        /// <param name="expense">The expense.</param>
        /// <exception cref="System.ArgumentNullException">expense</exception>
        public void InsertExpense(Expense expense)
        {
            if (expense == null)
                throw new ArgumentNullException("expense");

            _expenseRepository.Insert(expense);
        }

        /// <summary>
        /// Updates the expense.
        /// </summary>
        /// <param name="expense">The expense.</param>
        /// <exception cref="System.ArgumentNullException">expense</exception>
        public void UpdateExpense(Expense expense)
        {
            if (expense == null)
                throw new ArgumentNullException("expense");

            _expenseRepository.Update(expense);
        }

        /// <summary>
        /// Deletes the expense.
        /// </summary>
        /// <param name="expenseId">The expense id.</param>
        /// <exception cref="System.ArgumentNullException">expenseId</exception>
        public void DeleteExpense(Guid expenseId)
        {
            if (expenseId == null)
                throw new ArgumentNullException("expenseId");

            _expenseRepository.DeleteById(expenseId);
        }

        #endregion
    }
}